//********************************************************************************************************
// Product Name: MapWindow.dll Alpha
// Description:  Low level interfaces that allow separate components to use objects that are defined
//               in completely independant libraries so long as they satisfy the basic interface requirements.
//********************************************************************************************************
// The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); 
// you may not use this file except in compliance with the License. You may obtain a copy of the License at 
// http://www.mozilla.org/MPL/ 
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF 
// ANY KIND, either express or implied. See the License for the specificlanguage governing rights and 
// limitations under the License. 
//
// The Original Code is MapWindow.Interfaces
//
// The Initial Developer of this Original Code is Ted Dunsford. Created in October, 2007.
// 
// Contributor(s): (Open source contributors should list themselves and their modifications here). 
//
//********************************************************************************************************
using System;
using System.Collections.Generic;
using System.Text;
using MapWindow.Data;
using System.Drawing;
using MapWindow.Main.EventArguments;
using MapWindow.Geometries;

namespace MapWindow.Drawing
{
    /// <summary>
    /// A polygon with drawing characteristics
    /// </summary>
    public class MapPolygon : MapFeature, IMapPolygon
    {

        #region variables
        /// <summary>
        /// The parent layer
        /// </summary>
        protected IMapPolygonLayer _ParentMapPolygonLayer;
        /// <summary>
        /// The drawing characteristics for this specific polygon
        /// </summary>
        protected IPolygonSymbolizer _PolygonSymbolizer;

        /// <summary>
        /// The list of parts
        /// </summary>
        protected List<IBasicPolygon> _Parts;
      
       
        /// <summary>
        /// In order to not fill the holes, join the holes into a single "polygon" by appending each.  If more than
        /// one hole exists in a polygon, additional points may have to be added to ensure the fill polygon closes
        /// via the same route that it took to reach the additional interior polygons.
        /// </summary>
        protected List<PointF[]> _floatFillPoints;

        /// <summary>
        /// If we try to combine all the points into a single polygon when drawing the outlines, we will create
        /// problems.
        /// </summary>
        protected List<PointF[]> _floatBorderPoints;

        #endregion


        #region Constructors

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="fromFeature">The geometry and attribute providing feature elements</param>
        /// <param name="defaultSymbolizer">The default symbolizer to use if there is one</param>
        public MapPolygon(IFeature fromFeature, IPolygonSymbolizer defaultSymbolizer) : base(fromFeature, defaultSymbolizer)
        {
            // _BasicGeometry is set in the base constructor
            _Parts = new List<IBasicPolygon>();
            if (fromFeature != null)
            {
                if (fromFeature.FeatureType != FeatureTypes.Polygon)
                {
                    throw new InvalidOperationException("Cannot create a Polygon from geometries that have a feature type other than polygon.");
                }
                if (fromFeature.BasicGeometry != null)
                {
                    for (int iPart = 0; iPart < fromFeature.BasicGeometry.NumGeometries; iPart++)
                    {
                        _Parts.Add((IBasicPolygon)fromFeature.BasicGeometry.GetBasicGeometryN(iPart));
                    }
                }
            }
            _PolygonSymbolizer = defaultSymbolizer;
            

        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="basePolygon">The basic geometric and attribute properties of the polygon</param>
        public MapPolygon(IBasicPolygon basePolygon) : base()
        {
            _BasicGeometry = basePolygon;
            _Parts = new List<IBasicPolygon>();
            _Parts.Add((IBasicPolygon)new Polygon(basePolygon));
            _PolygonSymbolizer = new PolygonSymbolizer();
            _FeatureSymbolizer = (IFeatureSymbolizer)_PolygonSymbolizer;
            FillColor = Color.Beige;
            BorderPen = new Pen(Color.Black);
            _IsEmpty = false;
        }

        /// <summary>
        /// Constructs a Polygon with drawing characteristics
        /// </summary>
        /// <param name="fromFeature">a valid polygon feature</param>
        public MapPolygon(IFeature fromFeature) : base(fromFeature)
        {
            _Parts = new List<IBasicPolygon>();
            if (fromFeature != null)
            {
                if (fromFeature.FeatureType != FeatureTypes.Polygon)
                {
                    throw new InvalidOperationException("Cannot create a MapLineString from geometries that have a feature type other than line.");
                }
                if (fromFeature.BasicGeometry != null)
                {
                    for (int iPart = 0; iPart < fromFeature.BasicGeometry.NumGeometries; iPart++)
                    {
                        _Parts.Add((IBasicPolygon)fromFeature.BasicGeometry.GetBasicGeometryN(iPart));
                    }
                }
            }
            _BasicGeometry = fromFeature.BasicGeometry;
            _PolygonSymbolizer = new PolygonSymbolizer();
            _FeatureSymbolizer = (IFeatureSymbolizer)_PolygonSymbolizer;
            FillColor = Color.Beige;
            BorderPen = new Pen(Color.Black);
            _IsEmpty = false;
        }


        #endregion

        #region Methods

        /// <summary>
        /// Copies the drawing characteristics from the specified polygonSymbolizer
        /// </summary>
        /// <param name="polygonSymbolizer">Any valid IPolygonSymbolizer</param>
        public void Copy(IPolygonSymbolizer polygonSymbolizer)
        {
            _PolygonSymbolizer.Copy(polygonSymbolizer);
            
        }
       
        /// <summary>
        /// This actually draws the polygon using the characteristics on the polygon symbolizer.
        /// (Calling the Draw method on the symbolizer would only create a small rectangle
        /// representation of the polygon instead of actually drawing the polygon.)
        /// </summary>
        /// <param name="g">The System.Drawing.Graphics surface to draw on.</param>
        /// <param name="clipRectangle">The bounds within which to draw</param>
        /// <param name="target">This specifies the pixel "Fit To" rectangle</param>
        /// <exception cref="ArgumentNullException">g cannot be null.</exception>
        /// <exception cref="ArgumentNullException">Target cannot be empty.</exception>
        public override void Draw(System.Drawing.Graphics g, Rectangle clipRectangle, Rectangle target)
        {
            Pen scaledBorderPen; // A temporary copy of the drawing pens to adjust scale
            float minScale = 1;
            

            try
            {
                // show that we are actively drawing
                _IsBusy = true;
                
                if (this.Visible == false)
                {
                    // We won't get here if this was intercepted in a previous draw method.
                    OnPaintCancelled(new OperationCanceledException("Visible was false"), g, clipRectangle, target);
                    return;
                }

               

                // The OnBeforePainting method can cancel the drawing
                if (OnBeforePainting(g, clipRectangle, target) == true)
                {
                    OnPaintCancelled(new OperationCanceledException("Canceled by OnBeforePainting"), g, clipRectangle, target);
                    return;
                }

                // Many times an Envelope might not be defined yet, so this will simply exit in that case
                if (Envelope == null)
                {
                    OnAfterPainting(g, clipRectangle, target);
                    return;
                }

                if (Envelope.IsNull)
                {
                    OnAfterPainting(g, clipRectangle, target);
                    return;
                }

                // At this point, if a clip region is specified, apply it before any Transforms
                if (clipRectangle.IsEmpty == false)
                {
                    g.Clip = new Region(clipRectangle);
                }

                // Keep a backup in case we need to make changes to the transform
                System.Drawing.Drawing2D.Matrix oldMat = g.Transform;


                // Create a Transform if a target exists, otherwise, just use the existing transform
                if (target.IsEmpty == false)
                {
                    // Before applying any transforms, modify the clip region
                    g.IntersectClip(target);

                    // Create a transform in order to fit the Envelope into the Target
                    System.Drawing.Drawing2D.Matrix mat = new System.Drawing.Drawing2D.Matrix();


                    // While we are in pixel coordinates, translate to the specified Frame
                    mat.Translate((float)target.X, (float)target.Y);

                    // Scaling 
                    // An envelope width or height of 0 will throw an exception.
                    // I think that is better than simply not drawing, since it helps track the problem
                    float xScale = (float)(target.Width / Envelope.Width);
                    float yScale = (float)(target.Height / Envelope.Height);
                    minScale = Math.Min(xScale, yScale);

                    // Make yScale negative because in drawing space Y gets larger as you go down, but
                    // in world coordinates, Y gets larger as you go up.
                    mat.Scale(xScale, -yScale);

                    // Now that we are in world coordinates, pull the world envelope into view
                    // The top left corner is defined by the Minimum X and the Maximum Y
                    mat.Translate(-(float)Envelope.MinX, -(float)Envelope.MaxY);

                    // I control the transform this way because I can't seem to apply methods to the 
                    // graphics transform directly.
                    g.Transform = mat;

                }
                else
                {
                    // assume that a transformation has occured and that we need to calculate minScale from the matrix
                    float xScale = Math.Abs((float)(g.Transform.Elements[0]));
                    float yScale = Math.Abs((float)(g.Transform.Elements[3]));
                    minScale = Math.Min(xScale, yScale);
                }
               

                if(ScaleMode == ScaleModes.Pixels)
                {
                    scaledBorderPen = (Pen)BorderPen.Clone();
                    scaledBorderPen.Width = scaledBorderPen.Width / minScale;
                }
                else
                {
                    scaledBorderPen = BorderPen;
                }

                foreach (PointF[] Polygon in floatFillPoints)
                {
                    g.FillPolygon(FillBrush, Polygon, System.Drawing.Drawing2D.FillMode.Alternate);
                }

                // Actually Draw the shape
                if (BorderVisible == true)
                {
                    foreach (PointF[] Border in floatBorderPoints)
                    {
                       // g.FillPolygon(FillBrush, Border, System.Drawing.Drawing2D.FillMode.Alternate);
                        g.DrawPolygon(scaledBorderPen, Border);
                    }
                }

                // Set the trasform back to whatever it was before we drew the line string
                if(target.IsEmpty == false) g.Transform = oldMat;
                
            }
            catch (Exception ex)
            {
                OnPaintCancelled(ex, g, clipRectangle, target);
            }
            OnAfterPainting(g, clipRectangle, target);
        }

    

       
       

        #endregion


        #region Properties

        /// <summary>
        /// Gets or sets the color of the border, or outline for this point.
        /// If this is empty, the default for the layer will be used.
        /// </summary>
        public override System.Drawing.Color BorderColor
        {
            // This overrides the default handling to ensure the new 
            // default symbolizer is a PointSymbolizer
            set
            {
                if (_IsEmpty == true)
                {
                    PolygonSymbolizer = new PolygonSymbolizer(_PolygonSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.BorderColor = value;
            }
        }

        /// <summary>
        /// Gets or sets the float thickness for the border of this point.
        /// Setting this property will also affect the BorderPen property.
        /// If this is negative or 0, the default layer thickness is used.
        /// </summary>
        public override float BorderWidth
        {
            // This overrides the default handling to ensure the new 
            // default symbolizer is a PointSymbolizer
            set
            {
                if (_IsEmpty == true)
                {
                    PolygonSymbolizer = new PolygonSymbolizer(_PolygonSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.BorderWidth = value;
            }
        }

        /// <summary>
        /// Gets or sets the pen to use with the this point to draw the border.
        /// Setting this will also reset the BorderColor proprety.
        /// </summary>
        public override System.Drawing.Pen BorderPen
        {
            // This overrides the default handling to ensure the new 
            // default symbolizer is a PointSymbolizer
            set
            {
                if (_IsEmpty == true)
                {
                    PolygonSymbolizer = new PolygonSymbolizer(_PolygonSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.BorderPen = value;
            }
        }

        /// <summary>
        /// Gets or sets a boolean value that determines whether or not the default points for this layer
        /// will draw a border.  If this property is set to false, the other border properties will be ignored.
        /// </summary>
        public override bool BorderVisible
        {
            // This overrides the default handling to ensure the new 
            // default symbolizer is a PointSymbolizer
            set
            {
                if (_IsEmpty == true)
                {
                    PolygonSymbolizer = new PolygonSymbolizer(_PolygonSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.BorderVisible = value;
            }
        }

        /// <summary>
        /// Gets the exterior ring of the polygon as an ILineStringBase.
        /// This only returns the exterior ring of the first part.
        /// </summary>
        public virtual IBasicLineString ExteriorRing
        {
            get
            {
                if (_Parts == null) return null;
                if (_Parts.Count < 1) return null;
                return _Parts[0].ExteriorRing;
            }
        }


       
        /// <summary>
        /// Gets or sets the solid color to be used for filling this specific point.
        /// This will also change the FillBrush to a solid brush of the specified color.
        /// </summary>
        public override System.Drawing.Color FillColor
        {
            // This overrides the default handling to ensure the new 
            // default symbolizer is a PointSymbolizer
            set
            {
                if (_IsEmpty == true)
                {
                    PolygonSymbolizer = new PolygonSymbolizer(_PolygonSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.FillColor = value;
            }
        }

        /// <summary>
        /// Gets or sets the System.Drawing.Brush to be used when filling this point.
        /// Setting this value will also change the FillColor property, but only if
        /// the brush is a System.Drawing.SolidBrush.  Otherwise, the color property
        /// will be ignored.
        /// </summary>
        public override System.Drawing.Brush FillBrush
        {
            // This overrides the default handling to ensure the new 
            // default symbolizer is a PointSymbolizer
            set
            {
                if (_IsEmpty == true)
                {
                    PolygonSymbolizer = new PolygonSymbolizer(_PolygonSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.FillBrush = value;
            }
        }

        /// <summary>
        /// Converts the double coordinates into an array of drawing points
        /// </summary>
        public virtual List<PointF[]> floatFillPoints
        {
            get
            {
                PointF[] temp;
               
                if (_floatFillPoints == null)
                {
                    // The Drawing functions require PointF, so calculate this once and then store it
                    // so we don't have to re-calculate this every time we need to draw the lines
                    _floatFillPoints = new List<PointF[]>();



                    // Linestrings can be multi-part so we have a list of parts, each stored as an array of points
                    for (int iPart = 0; iPart < _BasicGeometry.NumGeometries; iPart++)
                    {
                        IBasicGeometry Part = _BasicGeometry.GetBasicGeometryN(iPart);
                        if (Part is IBasicPolygon)
                        {
                            // Cast this to a Polygon so that we can work with the holes
                            IBasicPolygon Polygon = (IBasicPolygon)Part;

                            // First convert the outer ring
                            IBasicLineString Shell = Polygon.ExteriorRing;

                            // Polygons return the total number of points in their outer and inner rings
                            int numPoints = Polygon.NumPoints;
                            
                            // We will add an additional point after each hole to ensure that the fill polygon
                            // retraces its steps when exiting from multiple holes
                            numPoints += Polygon.NumInteriorRings;
                           

                            // Set up an array to store the points
                            temp = new PointF[numPoints];

                            int iPoint = 0;

                            // Loop through all the points in the shell, converting them into float Points
                            for (int iShellPoint = 0; iShellPoint < Polygon.ExteriorRing.NumPoints; iShellPoint++)
                            {
                                temp[iPoint] = new PointF((float)Shell.GetCoordinateN(iShellPoint).X, (float)Shell.GetCoordinateN(iShellPoint).Y);
                                iPoint++;

                            }

                            PointF[] tail = new PointF[Polygon.NumInteriorRings];

                            // Now loop through each of the interior rings and do the same thing
                            for (int iHole = 0; iHole < Polygon.NumInteriorRings; iHole++)
                            {
                                IBasicLineString Hole = Polygon.InteriorRings[iHole];

                                // Loop through all the points in the shell, converting them into PointFs
                                for (int iHolePoint = 0; iHolePoint < Hole.NumPoints; iHolePoint++)
                                {
                                    temp[iPoint] = new PointF((float)Hole.GetCoordinateN(iHolePoint).X, (float)Hole.GetCoordinateN(iHolePoint).Y);
                                    iPoint++;
                                }

                                // the tail needs to remember how it got from hole to hole so that it can walk exactly backwards and close gaps
                                if (iHole < Polygon.NumInteriorRings - 1) // Don't add one for the very last hole
                                {
                                    temp[numPoints - iHole - 2] = new PointF((float)Hole.GetCoordinateN(0).X, (float)Hole.GetCoordinateN(0).Y);
                                }
                                
                            }


                            // add one for the shell, but only if we are comming back outside from a hole
                            if (Polygon.NumInteriorRings > 0)
                            {
                                temp[numPoints - 1] = new PointF((float)Shell.GetCoordinateN(0).X, (float)Shell.GetCoordinateN(0).Y);
                            }

                           
                           
                            //// Add the shell to the list
                            _floatFillPoints.Add(temp);
                        }
                    }

                }
                return _floatFillPoints;
            }
            set { _floatFillPoints = value; }
        }

        /// <summary>
        /// This is a faster mechanism for drawing border points
        /// </summary>
        public virtual List<PointF[]> floatBorderPoints
        {
            get
            {
                PointF[] temp;

                if (_floatBorderPoints == null)
                {
                    // The Drawing functions require PointF, so calculate this once and then store it
                    // so we don't have to re-calculate this every time we need to draw the lines
                    _floatBorderPoints = new List<PointF[]>();



                    // Linestrings can be multi-part so we have a list of parts, each stored as an array of points
                    for (int iPart = 0; iPart < _BasicGeometry.NumGeometries; iPart++)
                    {
                        IBasicGeometry Part = _BasicGeometry.GetBasicGeometryN(iPart);
                        if (Part is IBasicPolygon)
                        {
                            // Cast this to a Polygon so that we can work with the holes
                            IBasicPolygon Polygon = (IBasicPolygon)Part;

                            // First convert the outer ring
                            IBasicLineString Shell = Polygon.ExteriorRing;

                            int numShellPoints = Shell.NumPoints;


                            // Set up an array to store the points
                            temp = new PointF[numShellPoints];

                            // Loop through all the points in the shell, converting them into PointFs
                            for (int iShellPoint = 0; iShellPoint < numShellPoints; iShellPoint++)
                            {
                                temp[iShellPoint] = new PointF((float)Shell.GetCoordinateN(iShellPoint).X, (float)Shell.GetCoordinateN(iShellPoint).Y);
                            }

                            // Add the shell to the list
                            _floatBorderPoints.Add(temp);

                            // Now loop through each of the interior rings and do the same thing

                            for (int iHole = 0; iHole < Polygon.NumInteriorRings; iHole++)
                            {
                                IBasicLineString Hole = Polygon.InteriorRings[iHole];

                                int numHolePoints = Hole.NumPoints;


                                // Set up an array to store the points
                                temp = new PointF[numHolePoints];

                                // Loop through all the points in the shell, converting them into PointFs
                                for (int iHolePoint = 0; iHolePoint < numHolePoints; iHolePoint++)
                                {
                                    temp[iHolePoint] = new PointF((float)Hole.GetCoordinateN(iHolePoint).X, (float)Hole.GetCoordinateN(iHolePoint).Y);
                                }

                                // Add the shell to the list
                                _floatBorderPoints.Add(temp);

                            }
                        }
                    }
                }
                return _floatBorderPoints;
            }
            set { _floatBorderPoints = value; }
        }

        /// <summary>
        /// Gets the list of Interior Rings in the form of ILineStringBase objects,
        /// but only from the first part.  If this is a multipart polygon you will
        /// have to check each part for its interior rings.
        /// </summary>
        public virtual IBasicLineString[] InteriorRings
        {
            get
            {
                if (_Parts == null) return null;
                if (_Parts.Count < 1) return null;
                return _Parts[0].InteriorRings;
            }
        }


        /// <summary>
        /// Gets the count of holes or interior rings, but only for the first part.
        /// If this is a multipart polygon, then each part should be checked separately.
        /// </summary>
        public virtual int NumInteriorRings 
        {
            get 
            {
                if (_Parts == null) return 0;
                if (_Parts.Count < 1) return 0;
                return _Parts[0].NumInteriorRings; 
            }
        }

         /// <summary>
        /// Gets an integer count representing the number of parts in this feature.
        /// </summary>
        public int NumParts
        {
            get { return _Parts.Count; }
        }

        /// <summary>
        /// Gets or sets a float value from 0 to 1 where 0 is completely transparent
        /// and 1 is completely solid.  Setting an alpha of a specific feature, like
        /// FillColor, to something less than 255 will control that feature's transparency
        /// without affecting the others.  The final transparency of the feature will
        /// be that alpha multiplied by this value and rounded to the nearest byte.
        /// </summary>
        public override float Opacity
        {
            // Assume we previously referenced a PointLayer, and set the new
            // symbolizer to use the old one for default properties
            set
            {
                if (_IsEmpty == true)
                {
                    PolygonSymbolizer = new PolygonSymbolizer(_PolygonSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.Opacity = value;
            }
        }

        /// <summary>
        /// Gets or sets a valid IMapPolygonLayer
        /// </summary>
        public virtual IMapPolygonLayer ParentMapPolygonLayer
        {
            get
            {
                return _ParentMapPolygonLayer;
            }
            set
            {
                _ParentMapPolygonLayer = value;
            }
        }

         /// <summary>
        /// Gets or sets the list of BasicLineString parts that make up the
        /// entire set of linestrings to draw with this feature.
        /// </summary>
        public List<IBasicPolygon> Parts
        {
            get { return _Parts; }
            set
            {
                if (value == null)
                {
                    _Parts = new List<IBasicPolygon>();
                    _BasicGeometry = null;
                    return;
                }
                _Parts = value;
                if (_Parts.Count > 1)
                {
                    _BasicGeometry = new MultiPolygon(_Parts.ToArray());
                }
                else
                {
                    _BasicGeometry = new Polygon(_Parts[0]);
                }
            }
        }
        /// <summary>
        /// This specifies the specific polygon symbols to use for this polygon  
        /// </summary>
        public IPolygonSymbolizer PolygonSymbolizer
        {
            get { return _PolygonSymbolizer; }
            set 
            { 
                _PolygonSymbolizer = value;
                _FeatureSymbolizer = (IFeatureSymbolizer)_PolygonSymbolizer;
            }
        }

        /// <summary>
        /// Gets or Sets a ScaleModes enumeration that determines whether non-coordinate drawing
        /// properties like width or size use pixels or world coordinates.  If pixels are
        /// specified, a back transform is used to approximate pixel sizes. 
        /// </summary>
        public override ScaleModes ScaleMode
        {
            // Assume we previously referenced a PointLayer, and set the new
            // symbolizer to use the old one for default properties
            set
            {
                if (_IsEmpty == true)
                {
                    PolygonSymbolizer = new PolygonSymbolizer(_PolygonSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.ScaleMode = value;
            }
        }

        /// <summary>
        /// Gets or sets a boolean indicating whether or not this specific MapPoint should be drawn.
        /// </summary>
        public override bool Visible
        {
            // Assume we previously referenced a PointLayer, and set the new
            // symbolizer to use the old one for default properties
            set
            {
                if (_IsEmpty == true)
                {
                    PolygonSymbolizer = new PolygonSymbolizer(_PolygonSymbolizer);
                    _IsEmpty = false;
                }
                _FeatureSymbolizer.Visible = value;
            }
        }
        
       

        #endregion
    }
}
